# Module 'dirmp'
#
# Définit une classe pouvant servir de base à des outils de comparaison de
# répertoires.

import posix

import path

import dircache
import cmpcache
import statcache


# Ces constantes, tirées de <sys/stat.h>, représentent des types de fichier.
#
S_IFDIR = 4
S_IFREG = 8

# Extrait le type de fichier des données brutes obtenues grâce à la commande
# stat.
#
def S_IFMT(st): return st[0] / 4096


# Classe pour la comparaison de répertoires.
#
class dircmp():
	#
	def new(dd, (a, b)): # Initialisation
		dd.a = a
		dd.b = b
		# Ces propriétés peuvent être modifiées par l'appelant avant d'appeler
		# dd.run() :
		dd.hide = ['.', '..'] # Les noms à ne jamais afficher
		dd.ignore = ['RCS', 'tags'] # Les noms ignorés lors de la comparaison
		#
		return dd
	#
	def run(dd): # Compare tout excepté les sous-répertoires en commun
		dd.a_list = filter(dircache.listdir(dd.a), dd.hide)
		dd.b_list = filter(dircache.listdir(dd.b), dd.hide)
		dd.a_list.sort()
		dd.b_list.sort()
		dd.phase1()
		dd.phase2()
		dd.phase3()
	#
	def phase1(dd): # Trouve les noms en commun
		dd.a_only = []
		dd.common = []
		for x in dd.a_list:
			if x in dd.b_list:
				dd.common.append(x)
			else:
				dd.a_only.append(x)
		#
		dd.b_only = []
		for x in dd.b_list:
			if x not in dd.common:
				dd.b_only.append(x)
	#
	def phase2(dd): # Distingue entre fichiers, répertoires et autres
		dd.common_dirs = []
		dd.common_files = []
		dd.common_funny = []
		#
		for x in dd.common:
			a_path = path.cat(dd.a, x)
			b_path = path.cat(dd.b, x)
			#
			ok = 1
			try:
				a_stat = statcache.stat(a_path)
			except posix.error, why:
				# print 'Impossible d\'obtenir les statistiques du fichier ', a_path, ' : ', why[1]
				ok = 0
			try:
				b_stat = statcache.stat(b_path)
			except posix.error, why:
				# print 'Impossible d\'obtenir les statistiques du fichier ', b_path, ' : ', why[1]
				ok = 0
			#
			if ok:
				a_type = S_IFMT(a_stat)
				b_type = S_IFMT(b_stat)
				if a_type <> b_type:
					dd.common_funny.append(x)
				elif a_type = S_IFDIR:
					dd.common_dirs.append(x)
				elif a_type = S_IFREG:
					dd.common_files.append(x)
				else:
					dd.common_funny.append(x)
			else:
				dd.common_funny.append(x)
	#
	def phase3(dd): # Compare les fichiers en commun
		xx = cmpfiles(dd.a, dd.b, dd.common_files)
		dd.same_files, dd.diff_files, dd.funny_files = xx
	#
	def phase4(dd): # Compare les sous-répertoires en commun
		# Un nouvel objet dircmp est créé pour chaque sous-répertoire en
		# commun; il est stocké dans un dictionnaire, avec pour clé le nom
		# de fichier.
		# Les propriétés hide et ignore sont héritées de l'objet parent
		dd.subdirs = {}
		for x in dd.common_dirs:
			a_x = path.cat(dd.a, x)
			b_x = path.cat(dd.b, x)
			dd.subdirs[x] = newdd = dircmp().new(a_x, b_x)
			newdd.hide = dd.hide
			newdd.ignore = dd.ignore
			newdd.run()
	#
	def phase4_closure(dd): # Appelle de manière récursive phase4() pour
                            # traiter tous les sous-répertoires
		dd.phase4()
		for x in dd.subdirs.keys():
			dd.subdirs[x].phase4_closure()
	#
	def report(dd): # Affiche un rapport concernant les différences entre a et
                    # b
		# Suppose que les phases 1 à 3 ont été exécutées
		# Le format de la sortie générée par cette commande est volontairement
		# pourri
		print 'comparaison de', dd.a, 'et', dd.b
		if dd.a_only:
			print 'Seulement dans', dd.a, ' : ', dd.a_only
		if dd.b_only:
			print 'Seulement dans', dd.b, ' : ', dd.b_only
		if dd.same_files:
			print 'Fichiers identiques : ', dd.same_files
		if dd.diff_files:
			print 'Fichiers différents : ', dd.diff_files
		if dd.funny_files:
			print 'Fichiers en commun problématiques :', dd.funny_files
		if dd.common_dirs:
			print 'Sous-répertoires en commun :', dd.common_dirs
		if dd.common_funny:
			print 'Autres :', dd.common_funny
	#
	def report_closure(dd): # Affiche des rapports pour dd et ses
                            # sous-répertoires
		# Si la phase 4 n'a pas eu lieu, aucun rapport ne s'affiche pour les
		# sous-répertoires
		dd.report()
		try:
			x = dd.subdirs
		except NameError:
			return # Aucun sous-répertoire n'a été traité
		for x in dd.subdirs.keys():
			print
			dd.subdirs[x].report_closure()
	#
	def report_phase4_closure(dd): # Effectue un rapport et la phase 4 de
                                   # manière récursive
		dd.report()
		dd.phase4()
		for x in dd.subdirs.keys():
			print
			dd.subdirs[x].report_phase4_closure()


# Compare les fichiers en commun de deux répertoires.
# Renvoie :
#	- les fichiers qui sont égaux après comparaison
#	- les fichiers qui sont différents après comparaison
#	- les cas particuliers (quand on ne peut obtenir les statistiques etc)
#
def cmpfiles(a, b, common):
	res = ([], [], [])
	for x in common:
		res[cmp(path.cat(a, x), path.cat(b, x))].append(x)
	return res


# Compare deux fichiers.
# Renvoie :
#	0 en cas d'égalité
#	1 en cas de différence
#	2 pour les cas particuliers (quand on ne peut obtenir les statistiques etc)
#
def cmp(a, b):
	try:
		if cmpcache.cmp(a, b): return 0
		return 1
	except posix.error:
		return 2


# Enlève d'une liste un élément.
# NB : Ceci modifie directement la liste passée en argument.
#
def remove(list, item):
	for i in range(len(list)):
		if list[i] = item:
			del list[i]
			break


# Renvoie une copie de la liste passée en argument, les éléments se trouvant
# dans skip étant supprimés.
#
def filter(list, skip):
	result = []
	for item in list:
		if item not in skip: result.append(item)
	return result


# Démonstration et test.
#
def demo():
	import sys
	import getopt
	options, args = getopt.getopt(sys.argv[1:], 'r')
	if len(args) <> 2: raise getopt.error, 'je veux deux arguments'
	dd = dircmp().new(args[0], args[1])
	dd.run()
	if ('-r', '') in options:
		dd.report_phase4_closure()
	else:
		dd.report()

# demo()
